package com.tlgen.orm.utils;

import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import com.tlgen.orm.annotation.*;
import com.tlgen.orm.factory.QueryOperator;
import com.tlgen.orm.model.InsertParams;
import com.tlgen.orm.model.MiddleParams;
import com.tlgen.orm.model.ModelParams;
import com.tlgen.orm.model.RelationParams;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static com.tlgen.orm.constant.SQLScript.DEL_FLAG;
import static com.tlgen.orm.enums.Anno.ASSOCIATION;
import static com.tlgen.orm.enums.Anno.COLLECTION;
import static com.tlgen.orm.utils.ReflectionUtils.getObjectFieldValues;
import static com.tlgen.orm.utils.StringUtils.uncapUnderline;

/**
 * ORM 注解反射工具类
 */
public class ORMUtils {

    /**
     * 获取注解 PRI 对应的字段属性
     *
     * @param tClass
     * @param <T>
     * @return
     */
    public static <T> String getPrimaryKey(Class<T> tClass) {
        String primaryKey = "";
        // 先找自身类
        Field[] fields = tClass.getDeclaredFields();
        for (Field field : fields) {
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            // 找有 PRI 注解的属性, 获取对应的数据库字段名称和对应值
            Id anno = field.getAnnotation(Id.class);
            if (Objects.nonNull(anno)) {
                // 如果有 FieldName 注解, 则获取注解中的值作为数据库字段
                FieldName fieldNameAnno = field.getAnnotation(FieldName.class);
                if (null != fieldNameAnno) {
                    primaryKey = fieldNameAnno.value();
                } else {
                    // 如果没有 FieldName 注解, 则设置声明的字段作为数据库字段
                    primaryKey = uncapUnderline(field.getName());
                }
            }
        }
        // 如果没有, 再找父类
        if (StrUtil.isBlank(primaryKey) && !Objects.equals("Object", tClass.getSuperclass().getSimpleName())) {
            Class<? super T> superclass = tClass.getSuperclass();
            Field[] superFields = superclass.getDeclaredFields();
            for (Field field : superFields) {
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                // 找有 PRI 注解的属性, 获取对应的数据库字段名称和对应值
                Id anno = field.getAnnotation(Id.class);
                if (Objects.nonNull(anno)) {
                    // 如果有 FieldName 注解, 则获取注解中的值作为数据库字段
                    FieldName fieldNameAnno = field.getAnnotation(FieldName.class);
                    if (null != fieldNameAnno) {
                        primaryKey = fieldNameAnno.value();
                    } else {
                        // 如果没有 FieldName 注解, 则设置声明的字段作为数据库字段
                        primaryKey = uncapUnderline(field.getName());
                    }
                }
            }
        }
        return primaryKey;
    }

    /**
     * 获取注解 PRI 对应的字段属性
     *
     * @param t
     * @param <T>
     * @return
     */
    public static <T> String getPrimaryKey(T t) {
        String primaryKey = "";
        Class<?> tClass = t.getClass();
        Field[] fields = tClass.getDeclaredFields();
        for (Field field : fields) {
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            // 找有 PRI 注解的属性, 获取对应的数据库字段名称和对应值
            Id anno = field.getAnnotation(Id.class);
            if (Objects.nonNull(anno)) {
                // 如果有 FieldName 注解, 则获取注解中的值作为数据库字段
                FieldName fieldNameAnno = field.getAnnotation(FieldName.class);
                if (null != fieldNameAnno) {
                    primaryKey = fieldNameAnno.value();
                } else {
                    // 如果没有 FieldName 注解, 则设置声明的字段作为数据库字段
                    primaryKey = uncapUnderline(field.getName());
                }
            }
        }
        // 如果没有, 再找父类
        if (StrUtil.isBlank(primaryKey) && !Objects.equals("Object", tClass.getSuperclass().getSimpleName())) {
            Class<? super T> superclass = (Class<? super T>) tClass.getSuperclass();
            Field[] superFields = superclass.getDeclaredFields();
            for (Field field : superFields) {
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                // 找有 PRI 注解的属性, 获取对应的数据库字段名称和对应值
                Id anno = field.getAnnotation(Id.class);
                if (Objects.nonNull(anno)) {
                    // 如果有 FieldName 注解, 则获取注解中的值作为数据库字段
                    FieldName fieldNameAnno = field.getAnnotation(FieldName.class);
                    if (null != fieldNameAnno) {
                        primaryKey = fieldNameAnno.value();
                    } else {
                        // 如果没有 FieldName 注解, 则设置声明的字段作为数据库字段
                        primaryKey = uncapUnderline(field.getName());
                    }
                }
            }
        }
        return primaryKey;
    }

    /**
     * 获取实体类中一对一模型关联属性字段
     *
     * @param tClass
     * @param <T>
     * @return
     */
    public static <T> List<RelationParams> getAssociationFileName(Class<T> tClass) {
        // 获取类中属性上的注解和属性对应的值
        List<RelationParams> associationParamsList = Lists.newArrayList();
        try {
            // 实例化具体对象
            Object instance = tClass.newInstance();
            Field[] field = tClass.getDeclaredFields();
            if (null != field) {
                for (Field fie : field) {
                    if (!fie.isAccessible()) {
                        fie.setAccessible(true);
                    }
                    Association associationAnnon = fie.getAnnotation(Association.class);
                    Collection collectionAnnon = fie.getAnnotation(Collection.class);
                    if (null != associationAnnon) {
                        RelationParams params = new RelationParams();
                        // 获取属性字段名称
                        params.setColumnName(fie.getName());
                        // 获取属性字段数据类型
                        params.setColumnType(fie.getType().getName());
                        // 设置注解类型
                        params.setRelationType(ASSOCIATION.getValue());
                        // 获取注解中属性值
                        params.setRelationName(associationAnnon.relate());
                        // 设置属性字段值
                        params.setColumnValue("");
                        // 保存一对一模型关联数据
                        associationParamsList.add(params);
                    }
//                    if (null != collectionAnnon) {
//                        RelationParams params = new RelationParams();
//                        // 获取属性字段名称
//                        params.setColumnName(fie.getName());
//                        // 获取属性字段数据类型
//                        params.setColumnType(fie.getType().getName());
//                        // 设置注解类型
//                        params.setRelationType(COLLECTION.getValue());
//                        // 获取注解中属性值
//                        params.setRelationName(collectionAnnon.middle());
//                        // 设置属性字段值
//                        params.setColumnValue("");
//                        // 保存一对多模型关联数据
//                        associationParamsList.add(params);
//                    }
                }
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return associationParamsList;
    }


    /**
     * 获取实体类中模型关联一对多属性字段
     *
     * @param tClass
     * @param <T>
     * @return
     */
    public static <T> List<RelationParams> getCollectionFileName(Class<T> tClass) {
        // 获取类中属性上的注解和属性对应的值
        List<RelationParams> relationParamsList = Lists.newArrayList();
        Field[] field = tClass.getDeclaredFields();
        if (null != field) {
            for (Field fie : field) {
                if (!fie.isAccessible()) {
                    fie.setAccessible(true);
                }
                Collection collectionAnnon = fie.getAnnotation(Collection.class);
                if (null != collectionAnnon) {
                    RelationParams params = new RelationParams();
                    // 设置主表实体类类型
                    params.setMasterClass(tClass);
                    // 获取属性字段名称
                    params.setColumnName(fie.getName());
                    // 获取属性字段数据类型
                    params.setColumnType(fie.getType().getName());
                    // 设置注解类型
                    params.setRelationType(COLLECTION.getValue());
                    // 获取注解中属性值(获取中间表类型)
                    params.setRelationClass(collectionAnnon.middle());
                    // 设置属性字段值
                    params.setColumnValue("");
                    // 保存一对多模型关联数据
                    relationParamsList.add(params);
                }
            }
        }
        return relationParamsList;
    }


    /**
     * 获取类中数据库字段与对应值
     *
     * @return
     */
    public static <T> List<ModelParams> getFileNameValue(T t) {
        // 获取类中属性上的注解和属性对应的值
        List<ModelParams> modelParamsList = Lists.newArrayList();
        Class<?> tClass = t.getClass();
        try {
            // 实例化具体对象
            Object instance = tClass.newInstance();
            Field[] field = tClass.getDeclaredFields();
            if (null != field) {
                for (Field fie : field) {
                    if (!fie.isAccessible()) {
                        fie.setAccessible(true);
                    }
                    FieldName annon = fie.getAnnotation(FieldName.class);
                    if (null != annon) { // 如果属性上有 FieldName 注解, 获取真实列名作为数据库字段
                        Method[] meth = annon.annotationType().getDeclaredMethods();
                        for (Method me : meth) {
                            if (!me.isAccessible()) {
                                me.setAccessible(true);
                            }
                            ModelParams params = new ModelParams();
                            try {
                                // 给字段重新赋值
                                fie.set(instance, me.invoke(annon, null));
                                try {
                                    String fieldValue = getFieldValue(t, fie);
                                    String fieldName = String.valueOf(fie.get(instance));
                                    params.setColumn(fieldName);
                                    params.setValue(fieldValue);
                                    modelParamsList.add(params);
                                } catch (NoSuchMethodException e) {
                                    e.printStackTrace();
                                }
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (IllegalArgumentException e) {
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        }
                    } else { // 如果属性上没有 FieldName 注解, 默认获取声明的字段映射数据库字段
                        ModelParams params = new ModelParams();
                        params.setColumn(fie.getName());
                        try {
                            params.setValue(getFieldValue(t, fie));
                        } catch (NoSuchMethodException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                        modelParamsList.add(params);
                    }
                }
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return modelParamsList;
    }

    /**
     * 根据 TableName 注解获取对应实体类绑定的数据库表名
     *
     * @param tClass
     * @param <T>
     * @return
     */
    public static <T> String getTableName(Class<T> tClass) {
        // 获取类上的注解值
        String tableName = "";
        TableName anno = tClass.getAnnotation(TableName.class);
        if (anno != null) {
            Method[] met = anno.annotationType().getDeclaredMethods();
            for (Method me : met) {
                if (!me.isAccessible()) {
                    me.setAccessible(true);
                }
                try {
                    Object invoke = me.invoke(anno, null);
                    if (null != invoke) {
//                        System.out.println(invoke);
                        tableName = String.valueOf(invoke);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        } else { // 如果没有 @TableName 注解, 就使用类名驼峰转下划线获取表名
            tableName = StringUtils.classNameToTableName(tClass);
        }
        return tableName;
    }


    /**
     * 根据 TableName 注解获取对应实体类绑定的数据库表名
     *
     * @param t
     * @param <T>
     * @return
     */
    public static <T> String getTableName(T t) {
        // 获取类上的注解值
        String tableName = "";
        Class<?> tClass = t.getClass();
        TableName anno = tClass.getAnnotation(TableName.class);
        if (anno != null) {
            Method[] met = anno.annotationType().getDeclaredMethods();
            for (Method me : met) {
                if (!me.isAccessible()) {
                    me.setAccessible(true);
                }
                try {
                    Object invoke = me.invoke(anno, null);
                    if (null != invoke) {
                        System.out.println(invoke);
                        tableName = String.valueOf(invoke);
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
        return tableName;
    }


    /**
     * 反射获取类中所有属性
     *
     * @param t
     * @param <T>
     * @return
     */
    public static <T> List<InsertParams> getFieldList(T t) {
        List<InsertParams> paramsList = Lists.newArrayList();
        Field[] declaredFields = t.getClass().getDeclaredFields();
        for (Field field : declaredFields) {
            InsertParams params = new InsertParams();
            params.setFieldName(field.getName());
            try {
                params.setFieldValue(getFieldValue(t, field));
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            paramsList.add(params);

        }
        return paramsList;
    }


    /**
     * 根据属性的基本数据类型、包装类型调用其 getter 方法获取值
     *
     * @param t
     * @param field
     * @param <T>
     * @return
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public static <T> String getFieldValue(T t, Field field)
            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        // 属性值
        String value = "";
        // 判断是否有关联模型注解, 如果有就忽略
        Association associationAnnon = field.getAnnotation(Association.class);
        Collection collectionAnnon = field.getAnnotation(Collection.class);
        if (null == associationAnnon && null == collectionAnnon) {
            // 如果类型是  String
            if (field.getGenericType().toString().equals(
                    "class java.lang.String")) { // 如果type是类类型，则前面包含"class "，后面跟类名
                // 拿到该属性的 getter 方法
                Method m = (Method) t.getClass().getMethod(
                        "get" + getMethodName(field.getName()));
                String val = (String) m.invoke(t); // 调用 getter 方法获取属性值
                if (null != val) {
                    value = val;
                }
            }

            // 如果类型是 Integer
            if (field.getGenericType().toString().equals(
                    "class java.lang.Integer")) {
                Method m = (Method) t.getClass().getMethod(
                        "get" + getMethodName(field.getName()));
                Integer val = (Integer) m.invoke(t);
                if (val != null) {
                    value = String.valueOf(val);
                }

            }

            // 如果类型是 Double
            if (field.getGenericType().toString().equals(
                    "class java.lang.Double")) {
                Method m = (Method) t.getClass().getMethod(
                        "get" + getMethodName(field.getName()));
                Double val = (Double) m.invoke(t);
                if (val != null) {
                    value = String.valueOf(val);
                }

            }

            // 如果类型是 Boolean 是封装类
            if (field.getGenericType().toString().equals(
                    "class java.lang.Boolean")) {
                Method m = (Method) t.getClass().getMethod(
                        field.getName());
                Boolean val = (Boolean) m.invoke(t);
                if (val != null) {
                    value = String.valueOf(val);
                }

            }

            // 如果类型是  boolean 基本数据类型不一样 这里有点说名如果定义名是 isXXX 的 那就全都是 isXXX 的
            // 反射找不到 getter 的具体名
            if (field.getGenericType().toString().equals("boolean")) {
                Method m = (Method) t.getClass().getMethod(
                        field.getName());
                Boolean val = (Boolean) m.invoke(t);
                if (val != null) {
                    value = String.valueOf(val);
                }

            }
            // 如果类型是 Date
            if (field.getGenericType().toString().equals(
                    "class java.util.Date")) {
                Method m = (Method) t.getClass().getMethod(
                        "get" + getMethodName(field.getName()));
                Date val = (Date) m.invoke(t);
                if (val != null) {
                    value = String.valueOf(val);
                }

            }
            // 如果类型是 Short
            if (field.getGenericType().toString().equals(
                    "class java.lang.Short")) {
                Method m = (Method) t.getClass().getMethod(
                        "get" + getMethodName(field.getName()));
                Short val = (Short) m.invoke(t);
                if (val != null) {
                    value = String.valueOf(val);
                }
            }
        }
        // 返回属性值
        return value;
    }

    // 获取属性的 getter 方法名
    private static String getMethodName(String fieldName) {
        byte[] items = fieldName.getBytes();
        items[0] = (byte) ((char) items[0] - 'a' + 'A');
        return new String(items);
    }

    /**
     * 获取中间表映射数据
     *
     * @param tClass
     * @param <T>
     * @return
     */
    public static <T> List<MiddleParams> getMiddleMappingParams(Class<T> tClass) {
        List<MiddleParams> middleParamsList = Lists.newArrayList();
        // 获取中间表表名
        TableName tableNameAnnotation = tClass.getAnnotation(TableName.class);
        String tableName = "";
        if (null != tableNameAnnotation) {
            tableName = tableNameAnnotation.name();
        }
        // 获取映射
        Field[] fields = tClass.getDeclaredFields();
        for (Field field : fields) {
            // 获取映射的表名
            MappingClass annotation = field.getAnnotation(MappingClass.class);
            if (null != annotation) {
                // 中间表映射数据
                MiddleParams middleParams = new MiddleParams();
                // 设置中间表类型
//                middleParams.setMasterClass(tClass);
                // 设置中间表名称
                middleParams.setTableName(tableName);
                middleParams.setMappingClass(annotation.value());
                // 根据映射类型获取数据库表
                String mappingTableName = getTableName(annotation.value());
                middleParams.setMappingTableName(mappingTableName);
                // 获取映射的属性字段名
                FieldName fieldNameAnnotation = field.getAnnotation(FieldName.class);
                if (null != fieldNameAnnotation) {
                    middleParams.setMappingColumn(fieldNameAnnotation.value());
                } else {
                    middleParams.setMappingColumn(field.getName());
                }
                // 保存中间表映射数据
                middleParamsList.add(middleParams);
            }
        }
        return middleParamsList;
    }

    /**
     * 获取逻辑删除属性
     *
     * @param tclass
     * @param <T>
     * @return
     */
    public static <T> String getLogicalDeleteField(Class<T> tclass) {
        Class<? super T> superclass = tclass.getSuperclass();
        if (!Objects.equals("Object", superclass.getSimpleName())) {
            for (Field declaredField : superclass.getDeclaredFields()) {
                declaredField.setAccessible(true);
                if (declaredField.isAnnotationPresent(AsLogicalField.class)) {
                    return uncapUnderline(declaredField.getName());
                }
            }
        }
        for (Field declaredField : tclass.getDeclaredFields()) {
            declaredField.setAccessible(true);
            if (declaredField.isAnnotationPresent(AsLogicalField.class)) {
                return uncapUnderline(declaredField.getName());
            }
        }
        return DEL_FLAG;
    }

    public static <T> QueryOperator<T> getQueryOperator(Object object) {
        QueryOperator<T> queryOperator = new QueryOperator<>();
        Map<String, Object> map = getObjectFieldValues(object);
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (Objects.nonNull(entry.getValue())) {
                queryOperator.eq(entry.getKey(), entry.getValue());
            }
        }
        return queryOperator;
    }

}
